// Copyright (c) 2009, Anton Korenyushkin
// All rights reserved.

// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//     * Neither the name of the author nor the names of contributors may be
//       used to endorse or promote products derived from this software
//       without specific prior written permission.

// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

ak.use('ak');
ak.update(ak.global, ak);


function init() {
  dropRelVars(keys(db));
  db.Place.create(
    {
      id: 'unique serial',
      app: 'string',
      owner: 'string',
      spot: 'string',
      count: 'int'
    },
    'check (owner && spot) || (!owner && !spot)',
    'unique [app, owner, spot]');
  db.Record.create(
    {
      date: 'date',
      level: 'int',
      message: 'string',
      place: 'int -> Place.id',
      user: 'string'
    });
}


function test() {
  runTestSuite(include('tests.js'));
  return out.read();
}


var MainController = Controller.subclass(
  {
    get: function (request) {
      var user = request.user;
      return renderToResponse(
        'main.html',
        {
          user: user,
          adminedApps: getAdminedApps(user),
          developedApps: getDevelopedApps(user)
        });
    }
  }).decorated(Controller.requiringLogin);


function getPlacePage(request, place) {
  var records = db.Record.where({place: place.id});
  return renderToResponse(
    'place.html',
    {
      user: request.user,
      place: place,
      records: records
    });
}


var AppController = MainController.subclass(
  function (request, name) {
    MainController.call(this, request);
    try {
      this._descr = describeApp(name);
    } catch (error) {
      throw (error instanceof NoSuchAppError
             ? new NotFoundError('No such app: ' + repr(name))
             : error);
    }
    if (this._descr.developers.indexOf(request.user) == -1)
      throw new HttpError('You are not a developer of ' + repr(name) + ' app',
                          http.FORBIDDEN);
  },
  {
    _getReleasePlace: function (ignore/* = false */) {
      var result = db.Place.where(
          {
            app: this._descr.name,
            owner: '',
            spot: ''
          }).only('id', 'count')[0];
      if (!ignore && !result)
        throw new NotFoundError('No release records in app ' + repr(app));
      return result;
    },

    get: function (request) {
      return renderToResponse(
        'app.html',
        {
          releasePlace: this._getReleasePlace(true),
          descr: this._descr
        });
    },

    getReleasePage: function (request) {
      return getPlacePage(request, this._getReleasePlace());
    }
  });


var OwnerController = AppController.subclass(
  function (request, app, owner) {
    AppController.call(this, request, app);
    if (this._descr.developers.indexOf(owner) == -1)
      throw new NotFoundError(
        repr(owner) + ' is not a developer of ' + repr(app));
    this._owner = owner;
  },
  {
    get: function (request) {
      var releasePlace;
      if (this._owner == request.user)
        releasePlace = this._getReleasePlace(true);
      return renderToResponse(
        'owner.html',
        {
          app: this._descr.name,
          user: request.user,
          owner: this._owner,
          releasePlace: releasePlace,
          spotPlaces: db.Place.where({app: this._descr.name,
                                      owner: this._owner})
        });
    }
  });


var SpotController = OwnerController.subclass(
  function (request, app, owner, spot) {
    OwnerController.call(this, request, app, owner);
    this._spot = spot;
  },
  {
    get: function (request) {
      return getPlacePage(
        request,
        db.Place.get(
          {
            app: this._descr.name,
            owner: this._owner,
            spot: this._spot
          }));
    }
  });


defineRoutes('', MainController,
             [
               ['apps/',
                [
                  [AppController,
                   [
                     ['release/', AppController.page('Release')],
                     ['owners/',
                      [
                        [OwnerController,
                         [
                           ['spots/',
                            [
                              [SpotController]
                            ]]
                         ]]
                      ]]
                   ]]
                ]]
             ]);


function __main__(request) {
  if (!request.issuer)
    return defaultServe(request);
  if (request.method != 'post')
    return new Response('', http.METHOD_NOT_ALLOWED);
  if (request.path)
    return new Response('', http.NOT_FOUND);
  try {
    var data = JSON.parse(request.data);
  } catch (error) {
    return new Response(error.message, http.BAD_REQUEST);
  }
  if (typeof(data.level) != 'number' || isNaN(data.level) || data.level % 1)
    return new Response('Level must be integer', http.BAD_REQUEST);
  var placeObject = {
    app: request.issuer,
    owner: data.spot ? data.spot.owner : '',
    spot: data.spot ? data.spot.name : ''
  };
  var placeTuple = db.Place.where(placeObject).only('id')[0];
  if (placeTuple)
    db.Place.where({id: placeTuple.id}).update({count: 'count + 1'});
  else
    placeTuple = db.Place.insert(update(placeObject, {count: 1}));
  db.Record.insert(
    {
      date: new Date(),
      level: data.level,
      message: data.message,
      place: placeTuple.id,
      user: request.user
    });
  return new Response('', http.CREATED);
}
